package cn.iocoder.yudao.module.jikong.service.backup;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;
import java.util.*;
import java.io.*;
import java.time.LocalDateTime;

import cn.iocoder.yudao.module.jikong.controller.admin.backup.vo.*;
import cn.iocoder.yudao.module.jikong.dal.dataobject.backup.BackupDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;

import cn.iocoder.yudao.module.jikong.dal.mysql.backup.BackupMapper;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList;
import static cn.iocoder.yudao.module.jikong.enums.ErrorCodeConstants.*;

/**
 * 数据库备份 Service 实现类
 *
 * @author admin
 */
@Service
@Validated
@Slf4j
public class BackupServiceImpl implements BackupService {

    @Resource
    private BackupMapper backupMapper;

    @Value("${yudao.backup.path:/root/mysql/background}")
    private String backupPath;

    @Value("${spring.datasource.dynamic.datasource.master.url}")
    private String dbUrl;

    @Value("${spring.datasource.dynamic.datasource.master.username}")
    private String dbUsername;

    @Value("${spring.datasource.dynamic.datasource.master.password}")
    private String dbPassword;

    @Override
    public Long createBackup() {
        // 创建备份记录
        BackupDO backup = new BackupDO();
        String fileName = "backup_" + DateUtil.format(LocalDateTime.now(), "yyyyMMdd_HHmmss") + ".sql";
        backup.setSqlName(fileName);
        backup.setFilePath(backupPath + File.separator + fileName);
        backup.setStatus(0); // 0 表示生成中
        backupMapper.insert(backup);

        // 异步执行备份任务
        Long backupId = backup.getId();
        this.executeBackupAsync(backupId);

        return backupId;
    }

    /**
     * 异步执行备份任务
     * @param backupId 备份记录ID
     */
    private void executeBackupAsync(Long backupId) {
        new Thread(() -> {
            try {
                log.info("开始备份任务，backupId: {}", backupId);

                BackupDO backup = backupMapper.selectById(backupId);
                log.info("获取到的备份记录: {}", backup);

                // 解析数据库连接信息
                String[] dbInfo = parseDatabaseInfo();
                log.info("解析到的数据库连接信息: dbHost={}, dbPort={}, dbName={}", dbInfo[0], dbInfo[1], dbInfo[2]);
                String dbHost = dbInfo[0];
                String dbPort = dbInfo[1];
                String dbName = dbInfo[2];

                // 创建备份目录
                FileUtil.mkdir(backupPath);
                log.info("备份目录创建完成: {}", backupPath);

                // 构建mysqldump命令
                String command = String.format("mysqldump -h%s -P%s -u%s -p%s %s",
                        dbHost, dbPort, dbUsername, dbPassword, dbName);
                log.info("生成的mysqldump命令: {}", command);

                // 使用cmd执行命令（Windows）或bash执行命令（Linux）
                String[] cmd;
                String osName = System.getProperty("os.name").toLowerCase();
                log.info("当前操作系统: {}", osName);
                if (osName.contains("win")) {
                    // Windows系统
                    cmd = new String[]{"cmd", "/c", command + " > \"" + backup.getFilePath() + "\""};
                } else {
                    // Unix/Linux/Mac系统
                    cmd = new String[]{"sh", "-c", command + " > \"" + backup.getFilePath() + "\""};
                }
                log.info("将要执行的命令: {}", Arrays.toString(cmd));

                // 执行备份命令
                ProcessBuilder processBuilder = new ProcessBuilder(cmd);
                processBuilder.redirectErrorStream(true);
                Process process = processBuilder.start();
                log.info("mysqldump命令已启动");

                // 读取输出以避免缓冲区满
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        log.info("mysqldump输出: {}", line);
                    }
                }
                log.info("mysqldump命令输出读取完成");

                int exitCode = process.waitFor();
                log.info("mysqldump命令退出码: {}", exitCode);

                // 更新备份记录状态
                BackupDO updateObj = new BackupDO();
                updateObj.setId(backupId);
                if (exitCode == 0) {
                    updateObj.setStatus(1); // 1 表示生成成功
                    log.info("备份成功，更新备份记录状态为成功");
                } else {
                    updateObj.setStatus(2); // 2 表示生成失败
                    log.info("备份失败，更新备份记录状态为失败");
                }
                backupMapper.updateById(updateObj);
                log.info("备份记录更新完成");
            } catch (Exception e) {
                // 更新为失败状态
                BackupDO updateObj = new BackupDO();
                updateObj.setId(backupId);
                updateObj.setStatus(2); // 2 表示生成失败
                backupMapper.updateById(updateObj);
                log.error("备份任务执行过程中发生异常", e);
            }
        }).start();
    }


    /**
     * 解析数据库连接URL，提取主机、端口和数据库名
     * @return [host, port, databaseName]
     */
    private String[] parseDatabaseInfo() {
        // 示例URL: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai
        String url = dbUrl;

        // 移除jdbc前缀
        String withoutPrefix = url.substring("jdbc:mysql://".length());

        // 分离主机端口部分和参数部分
        String hostPortAndDb = withoutPrefix;
        if (withoutPrefix.contains("?")) {
            hostPortAndDb = withoutPrefix.substring(0, withoutPrefix.indexOf("?"));
        }

        // 分离数据库名
        String[] parts = hostPortAndDb.split("/");
        String hostAndPort = parts[0];
        String dbName = parts[1];

        // 分离主机和端口
        String host = hostAndPort;
        String port = "3306"; // 默认端口
        if (hostAndPort.contains(":")) {
            String[] hostPortParts = hostAndPort.split(":");
            host = hostPortParts[0];
            port = hostPortParts[1];
        }

        return new String[]{host, port, dbName};
    }

    @Override
    public void restoreBackup(Long id) {
        BackupDO backup = backupMapper.selectById(id);
        if (backup == null) {
            throw exception(BACKUP_NOT_EXISTS);
        }

        if (backup.getStatus() != 1) {
            throw exception(BACKUP_RESTORE_FAIL_STATUS_ERROR);
        }

        try {
            // 解析数据库连接信息
            String[] dbInfo = parseDatabaseInfo();
            String dbHost = dbInfo[0];
            String dbPort = dbInfo[1];
            String dbName = dbInfo[2];

            // 构建mysql命令
            String command = String.format("mysql -h%s -P%s -u%s -p%s %s",
                dbHost, dbPort, dbUsername, dbPassword, dbName);

            // 使用cmd执行命令（Windows）或bash执行命令（Linux）
            String[] cmd;
            String osName = System.getProperty("os.name").toLowerCase();
            if (osName.contains("win")) {
                // Windows系统
                cmd = new String[]{"cmd", "/c", "type \"" + backup.getFilePath() + "\" | " + command};
            } else {
                // Unix/Linux/Mac系统
                cmd = new String[]{"sh", "-c", command + " < \"" + backup.getFilePath() + "\""};
            }

            // 执行恢复命令
            ProcessBuilder processBuilder = new ProcessBuilder(cmd);
            processBuilder.redirectErrorStream(true);
            Process process = processBuilder.start();

            // 读取输出以避免缓冲区满
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    log.info("mysql restore output: {}", line);
                }
            }

            int exitCode = process.waitFor();

            if (exitCode != 0) {
                throw exception(BACKUP_RESTORE_FAIL);
            }
        } catch (Exception e) {
            throw exception(BACKUP_RESTORE_FAIL);
        }
    }

    @Override
    public void updateBackup(BackupSaveReqVO updateReqVO) {
        // 校验存在
        validateBackupExists(updateReqVO.getId());
        // 更新
        BackupDO updateObj = BeanUtils.toBean(updateReqVO, BackupDO.class);
        backupMapper.updateById(updateObj);
    }

    @Override
    public void deleteBackup(Long id) {
        // 校验存在
        validateBackupExists(id);
        // 删除
        backupMapper.deleteById(id);
    }

    @Override
        public void deleteBackupListByIds(List<Long> ids) {
        // 校验存在
        validateBackupExists(ids);
        // 删除
        backupMapper.deleteByIds(ids);
        }

    private void validateBackupExists(List<Long> ids) {
        List<BackupDO> list = backupMapper.selectByIds(ids);
        if (CollUtil.isEmpty(list) || list.size() != ids.size()) {
            throw exception(BACKUP_NOT_EXISTS);
        }
    }

    private void validateBackupExists(Long id) {
        if (backupMapper.selectById(id) == null) {
            throw exception(BACKUP_NOT_EXISTS);
        }
    }

    @Override
    public BackupDO getBackup(Long id) {
        return backupMapper.selectById(id);
    }

    @Override
    public PageResult<BackupDO> getBackupPage(BackupPageReqVO pageReqVO) {
        return backupMapper.selectPage(pageReqVO);
    }

}
